/*
 * Created on 11/24/2012 - 4:13 PM
 */
package sembr.postprocessing;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import vtk.vtkStringArray;

/**
 * SembrPostprocessing class is used to work with hdf5 data
 *
 * @author adriano
 */

public class SembrPostprocessing 
{    
    private String projectPath;
    private String fieldComponent;
    private int[] objectBoundsForHDF5;
    private String geometryDimension;    
    private String simulationDimension;
    List<String> processingOutput;
    private vtkStringArray fileNames;
            
    public SembrPostprocessing(String projectPath, String fieldComponent) 
    {
        this.projectPath = projectPath;
        this.fieldComponent = fieldComponent;
        processingOutput = new ArrayList<>();
        setGeometryDimension();
        setSimulationDimension();
        fileNames = new vtkStringArray();
    }
    
    public SembrPostprocessing(String projectPath, String fieldComponent, int[] objectBoundForH5) 
    {
        this.projectPath = projectPath;
        this.fieldComponent = fieldComponent;
        processingOutput = new ArrayList<>();
        setGeometryDimension();
        setSimulationDimension();
        setObjectBoundsForHDF5(objectBoundForH5);
        fileNames = new vtkStringArray();
    }
    
    public SembrPostprocessing(String filePath)
    {
        processingOutput = new ArrayList<>();
        setSimulationDimension(filePath);        
        splitSimulationDimension(simulationDimension);
        fileNames = new vtkStringArray();
    }
        
    /**
     * @return the projectPath
     */
    public String getProjectPath() 
    {
        return projectPath;
    }

    /**
     * @param projectPath the projectPath to set
     */
    public void setProjectPath(String projectPath) 
    {
        this.projectPath = projectPath;       
    }
    
    /**
     * @return the fieldComponent
     */
    public String getFieldComponent() 
    {
        return fieldComponent;
    }
    
    public String getGeometryDimension()
    {
        return geometryDimension;
    }
    
    public String getSimulationDimension()
    {
        return simulationDimension;
    }  
        
    /**
     * @return the objectBoundsForHDF5
     */
    public int[] getObjectBoundsForHDF5() 
    {
        return objectBoundsForHDF5;
    }
    
    public vtkStringArray getFileNames()
    {
        return fileNames;
    }

    /**
     * @param objectBoundsForHDF5 the objectBoundsForHDF5 to set
     */
    public final void setObjectBoundsForHDF5(int[] objectBoundsForHDF5) 
    {
        this.objectBoundsForHDF5 = objectBoundsForHDF5;
    }
    
    public void generateImagesForField(String dimension, 
                                       String initialSlice, 
                                       String endSlice, 
                                       String startTime, 
                                       String endTime,
                                       String timeStep 
                                      )
    {
        int firstSlice = Integer.valueOf(initialSlice);
        int finalSlice = Integer.valueOf(endSlice);
        int initialObjectSlice;
        int finalObjectSlice;
        
        switch (dimension) 
        {
            case "x":
                initialObjectSlice = getObjectBoundsForHDF5()[0];
                finalObjectSlice = getObjectBoundsForHDF5()[1];
                break;
            case "y":
                initialObjectSlice = getObjectBoundsForHDF5()[2];
                finalObjectSlice = getObjectBoundsForHDF5()[3];
                break;
            case "t": // for bidimensional post-processing movie
                initialObjectSlice = firstSlice;    
                finalObjectSlice = finalSlice;
                break;
            default:
                initialObjectSlice = getObjectBoundsForHDF5()[4];
                finalObjectSlice = getObjectBoundsForHDF5()[5];
                break;
        }  
        
        boolean[] isOutsideObjectBounds = isOutsideObjectBounds(firstSlice, finalSlice, initialObjectSlice, finalObjectSlice);        
                               
        String tempFinalSlice;
        String tempFirstSlice;
        List<String> scriptToLeftDomain = null;
        List<String> scriptToRightDomain = null;
        List<String> scriptToObject;

        if (isOutsideObjectBounds[0] && isOutsideObjectBounds[1])
        {
            // first object's bound becomes last slice
            // to create the correct pictures of left domain
            tempFinalSlice = String.valueOf(initialObjectSlice - 1);                
            scriptToLeftDomain = buildH5ToPngDomainScript(dimension, initialSlice, tempFinalSlice, startTime, endTime, timeStep);
            addNamesOfGeneratedFiles(dimension, initialSlice, tempFinalSlice);
            
            // create images to object
            tempFirstSlice = String.valueOf(initialObjectSlice);
            tempFinalSlice = String.valueOf(finalObjectSlice);
            scriptToObject = buildH5ToPngObjectScript(dimension, tempFirstSlice, tempFinalSlice, startTime, endTime, timeStep);
            addNamesOfGeneratedFiles(dimension, tempFirstSlice, tempFinalSlice);
            
            // final object's bound becomes first slice
            // endSlice put by user is the final slice
            tempFirstSlice = String.valueOf(finalObjectSlice + 1);                               
            scriptToRightDomain = buildH5ToPngDomainScript(dimension, tempFirstSlice, endSlice, startTime, endTime, timeStep);
            addNamesOfGeneratedFiles(dimension, tempFirstSlice, endSlice);
        }            
        else if (isOutsideObjectBounds[0] && !isOutsideObjectBounds[1])
        {
            // firstSlice put by user is on left domain
            // first object's bound becomes last slice
            // to create the correct pictures of left domain
            tempFinalSlice = String.valueOf(initialObjectSlice);                
            scriptToLeftDomain = buildH5ToPngDomainScript(dimension, initialSlice, tempFinalSlice, startTime, endTime, timeStep);
            addNamesOfGeneratedFiles(dimension, initialSlice, tempFinalSlice);
            
            // create images to object
            // end slice is on bound or inside object
            tempFirstSlice = String.valueOf(initialObjectSlice);
            scriptToObject = buildH5ToPngObjectScript(dimension, tempFirstSlice, endSlice, startTime, endTime, timeStep);
            addNamesOfGeneratedFiles(dimension, tempFirstSlice, endSlice);
        }   
        else if (!isOutsideObjectBounds[0] && isOutsideObjectBounds[1])
        {
            // create images to object
            // initalSlice is on bound or inside object
            tempFinalSlice = String.valueOf(finalObjectSlice);
            scriptToObject = buildH5ToPngObjectScript(dimension, initialSlice, tempFinalSlice, startTime, endTime, timeStep);
            addNamesOfGeneratedFiles(dimension, initialSlice, tempFinalSlice);
            
            // endSlice put by user is on right domain
            // final object's bound becomes first slice
            // endSlice put by user is the final slice
            tempFirstSlice = String.valueOf(finalObjectSlice);
            scriptToRightDomain = buildH5ToPngDomainScript(dimension, tempFirstSlice, endSlice, startTime, endTime, timeStep);
            addNamesOfGeneratedFiles(dimension, tempFirstSlice, endSlice);
        }
        else
        {
            // create images to object
            // initial and end slices are on bounds or inside object
            scriptToObject = buildH5ToPngObjectScript(dimension, initialSlice, endSlice, startTime, endTime, timeStep);
        }
            
        // clear array
        processingOutput.clear();

        if (scriptToLeftDomain != null)
        {
            executeProcess(scriptToLeftDomain);
        }

        if (scriptToRightDomain != null)
        {
            executeProcess(scriptToRightDomain);
        }

        if (scriptToObject != null)
        {
            executeProcess(scriptToObject);
        }
    }
    
    // for MIT-MPB Testing
    public void generateImagesForField(String filePath, String dataset)
    {
        List<String> commandListForRealField = buildH5ToPngObjectScript("z.r", filePath);        
        executeProcess(commandListForRealField);
        addNamesOfGeneratedFiles(filePath);
    }
        
    public void generateMovie2D(String width, String height, String framesPerSecond, String fileName)
    {
        executeProcess(buildScriptToMencoder(width, height, framesPerSecond, fileName));        
    }
    
    public ArrayList<String> getFileNamesAsJavaArray()
    {
        ArrayList<String> javaFileNames = new ArrayList<>();
        
        vtkStringArray fileNamesArray = getFileNames();
        
        if (fileNamesArray != null)
        {
            for (int i = 0; i < fileNamesArray.GetNumberOfValues(); i++)
            {
                javaFileNames.add(fileNamesArray.GetValue(i));
            }
        }
        
        return javaFileNames;
    }
        
    private void setGeometryDimension()
    {
        List<String> command = new ArrayList<>();
        command.add("h5ls");
        command.add(getProjectPath() + "/post_processing/eps-000000.00.h5");
        
        geometryDimension = executeProcess(command).get(0);       
    }
       
    private void setSimulationDimension()
    {
        List<String> command = new ArrayList<>();
        command.add("h5ls");
        command.add(getProjectPath() + "/post_processing/" + getFieldComponent() + "-000000.00.h5");
            
        simulationDimension = executeProcess(command).get(1);
    }
    
    private void setSimulationDimension(String filePath)
    {
        List<String> command = new ArrayList<>();
        command.add("h5ls");
        command.add(filePath);
        
        List<String> executeProcess = executeProcess(command);
        
        if (!executeProcess.isEmpty())
        {
            for (int i = 0; i < executeProcess.size(); i++)
            {
                simulationDimension = simulationDimension + executeProcess.get(i);
            }   
        }
    }
    
    private List<String> buildH5ToPngObjectScript(String dimension, String initialSlice, String endSlice, String startTime, String endTime, String timeStep)
    {
        List<String> commandList = new ArrayList<>();       
        commandList.add("h5topng");
        commandList.add("-v");
        commandList.add("-" + dimension + " " + initialSlice + ":" + endSlice);        
        commandList.add("-t" + " " + startTime + ":" + timeStep + ":" + endTime);
        commandList.add("-R");
        commandList.add("-Zc");
        commandList.add("/usr/share/h5utils/colormaps/dkbluered");
        commandList.add("-a");
        commandList.add("/usr/share/h5utils/colormaps/gray:0.3");
        commandList.add("-A");
        commandList.add(getProjectPath() + "/post_processing/eps-000000.00.h5");
        commandList.add(getProjectPath() + "/post_processing/" + getFieldComponent() + "-000000.00.h5");
             
        return commandList;       
    }
    
    /*
     * param dataset: related domain either real or complex
     */
    private List<String> buildH5ToPngObjectScript(String dataset, String filePath)
    {
        List<String> commandList = new ArrayList<>();   
        commandList.add("h5topng");
        commandList.add("-v");
        commandList.add("-R");
        commandList.add("-Zc");
        commandList.add("/usr/share/h5utils/colormaps/dkbluered");
        commandList.add("-S");
        commandList.add("5.0");
        commandList.add("-d");
        commandList.add(dataset);
        commandList.add("-a");
        commandList.add("/usr/share/h5utils/colormaps/gray:0.3");
        commandList.add("-A");
        commandList.add(getProjectPath() + "/post_processing/epsilon.h5");
        commandList.add(filePath);
        
        return commandList;
    }
    
    private List<String> buildH5ToPngDomainScript(String dimension, String initialSlice, String endSlice, String startTime, String endTime, String timeStep)
    {
        List<String> commandList = new ArrayList<>();
        
        commandList.add("h5topng");
        commandList.add("-v");
        commandList.add("-" + dimension + " " + initialSlice + ":" + endSlice);        
        commandList.add("-t" + " " + startTime + ":" + timeStep + ":" + endTime);
        commandList.add("-R");
        commandList.add("-Zc");
        commandList.add("/usr/share/h5utils/colormaps/dkbluered");
        commandList.add(getProjectPath() + "/post_processing/" + getFieldComponent() + "-000000.00.h5");
        
        return commandList;        
    }
    
    private List<String> buildScriptToMencoder(String width, String height, String framesPerSecond, String fileName)
    {
        List<String> commandList = new ArrayList<>();
        commandList.add("mencoder");
        commandList.add("mf://" + getProjectPath() + "/post_processing/" + getFieldComponent() + "-000000.00.t*.png");
        commandList.add("-mf");
        commandList.add("w=" + width + ":h=" + height + ":fps=" + framesPerSecond + ":type=png");
        commandList.add("-ovc");
        commandList.add("copy");
        commandList.add("-oac");
        commandList.add("copy");
        commandList.add("-o");
        commandList.add(getProjectPath() + "/post_processing/" + fileName + ".mpg");        
        
        return commandList;
    }
        
    private boolean[] isOutsideObjectBounds(int firstSlice, int finalSlice, int leftObjectBound, int rightObjectBound)
    {
        boolean[] isLeftOrRightOutsideObject = new boolean[2];
        
        if (firstSlice < leftObjectBound)
            isLeftOrRightOutsideObject[0] = true;
        else
            isLeftOrRightOutsideObject[0] = false;
        
        if (finalSlice > rightObjectBound)
            isLeftOrRightOutsideObject[1] = true;
        else
            isLeftOrRightOutsideObject[1] = false;
        
        return isLeftOrRightOutsideObject;
    }
            
    private List<String> executeProcess(List<String> command)
    {
        try 
        {
            Process process;
            InputStream is;
            InputStreamReader isr;
            BufferedReader br;            
            BufferedReader bre;
            
            process = new ProcessBuilder(command).start();

            is = process.getInputStream();
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);            
            bre = new BufferedReader(new InputStreamReader(process.getErrorStream()));            
            String line;
            
            while ((line = br.readLine()) != null) 
            {
                processingOutput.add(line);
                System.out.println(line);
            }

            while ((line = bre.readLine()) != null) 
            {
                processingOutput.add(line);
                System.out.println(line);
            }
        } 
        catch (IOException ex) 
        {
            Logger.getLogger(SembrPostprocessing.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return processingOutput;
    }
    
    private void addNamesOfGeneratedFiles(String dimension, String initialSlice, String endSlice)
    {
        int count = 0;
        
        String prefix = getFieldComponent() + "-000000.00." + dimension;
        String currentFileName;
        
        int firstSlice = Integer.valueOf(initialSlice).intValue();
        int finalSlice = Integer.valueOf(endSlice).intValue();
        
        for (int i = firstSlice; i <= finalSlice; i++)
        {
            if (finalSlice < 100 && i < 10)
            {
                currentFileName = prefix + "0" + String.valueOf(i);
                fileNames.InsertNextValue(getProjectPath() + File.separator + "post_processing/" + currentFileName + ".png");
            }
            else if (finalSlice < 100 && i >= 10)
            {
                currentFileName = prefix + String.valueOf(i);
                fileNames.InsertNextValue(getProjectPath() + File.separator + "post_processing/" + currentFileName + ".png");
            }
            else if (finalSlice >= 100 && i < 10)
            {
                currentFileName = prefix + "00" + String.valueOf(i);
                fileNames.InsertNextValue(getProjectPath() + File.separator + "post_processing/" +  currentFileName + ".png");
            }   
            else if (finalSlice >= 100 && i >= 10 && i < 100)
            {
                currentFileName = prefix + "0" + String.valueOf(i);
                fileNames.InsertNextValue(getProjectPath() + File.separator + "post_processing/" +  currentFileName + ".png");
            }
            else if (finalSlice >= 100 && i >= 10 && i >= 100)
            {
                currentFileName = prefix + String.valueOf(i);
                fileNames.InsertNextValue(getProjectPath() + File.separator + "post_processing/" +  currentFileName + ".png");
            }
            
            count++;
        }
        
        // System.out.println("Quantos loops pra gerar nomes?: " + count);
    }
    
    private void addNamesOfGeneratedFiles(String path)
    {
        int indexOf = path.indexOf("h5");
        String substring = path.substring(0, indexOf);
        String fileName = substring + "png";
        
        fileNames.InsertNextValue(fileName);
    }

    public double[] getFieldRange()
    {       
        int size = processingOutput.size();
        String lastItem = processingOutput.get(size - 1);
        return getRange(lastItem);
    }
    
    private double[] getRange(String textItem)
    {
        double range[] = new double[2];
        int length = textItem.length();
        String substring = textItem.substring(20, length - 1);
        int indexOf = substring.indexOf("to");
        String leftRangeValue = substring.substring(0, indexOf - 1).trim();
        String rightRangeValue = substring.substring(indexOf + 2).trim();
        
        range[0] = Double.valueOf(leftRangeValue).doubleValue();
        range[1] = Double.valueOf(rightRangeValue).doubleValue();
        
        return range;
    }
    
    private int[] splitSimulationDimension(String text)
    {
        if (text == null) return null;
        String delimiter = " ";
        String[] slicedProperties;
        ArrayList<String> simulationProperties = new ArrayList<>();
        
        slicedProperties = text.split(delimiter);     
        
        // beginning of 'Dataset' sub-string in MIT-MPB post processing
        int i = 60;
        
        while (i <= (slicedProperties.length - 1))
        {
            simulationProperties.add(slicedProperties[i]);
            i++;
        }
        
        int xLength, yLength, zLength, timeLength;
        String x, y, z, time;
        
        int lastIndex = simulationProperties.size() - 1;
        
        if ("".equals(simulationProperties.get(lastIndex - 3))) // bidimensional
        {
                xLength = simulationProperties.get(lastIndex - 1).length();
                yLength = simulationProperties.get(lastIndex).length();
                // timeLength = simulationProperties.get(lastIndex).length();
        
                x = simulationProperties.get(lastIndex - 1).substring(1, xLength - 1);
                y = simulationProperties.get(lastIndex).substring(0, yLength - 1);
                z = "0";
                // time = simulationProperties.get(lastIndex).substring(0, timeLength - 5);
        }
        else 
        { 
                xLength = simulationProperties.get(lastIndex - 2).length();
                yLength = simulationProperties.get(lastIndex - 1).length();
                zLength = simulationProperties.get(lastIndex).length();
                // timeLength = simulationProperties.get(lastIndex).length();
        
                x = simulationProperties.get(lastIndex - 3).substring(1, xLength - 1);
                y = simulationProperties.get(lastIndex - 2).substring(0, yLength - 1);
                z = simulationProperties.get(lastIndex - 1).substring(0, zLength - 1);
                // time = simulationProperties.get(lastIndex).substring(0, timeLength - 5);    
        }
        
        int dimensions[] = new int[3];
        dimensions[0] = Integer.valueOf(x);
        dimensions[1] = Integer.valueOf(y); 
        dimensions[2] = Integer.valueOf(z);
        // dimensions[3] = Integer.valueOf(time);
        
        return dimensions;
    }
}